/*
 * $Id: LoggerInterfaceUtils.java 114 2013-01-16 07:33:27Z vladimirow@mail.ru $
 * $HeadURL: http://ultralog.googlecode.com/svn/ultralog/tags/release-20130123/src/java/com/google/code/ultralog/factory/LoggerInterfaceUtils.java $
 *
 * This file is a part of ultralog project (http://code.google.com/p/ultralog/).
 * Released under New BSD License.
 */
package com.google.code.ultralog.factory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.google.code.ultralog.LogLevel;
import com.google.code.ultralog.LogLevelChecker;
import com.google.code.ultralog.LogMessage;
import com.google.code.ultralog.Logger;
import com.google.code.ultralog.factory.LoggerInterfaceDescriptor.LogLevelCheckerDescriptor;
import com.google.code.ultralog.factory.LoggerInterfaceDescriptor.LogLevelDescriptor;
import com.google.code.ultralog.factory.LoggerInterfaceDescriptor.LogMessageDescriptor;

/**
 * Contains various useful methods to work with logger interfaces.
 *
 * @author Mikhail Vladimirov
 */
public class LoggerInterfaceUtils
{
    private LoggerInterfaceUtils ()
    {
        throw new UnsupportedOperationException ("Do not instantiate me");
    }

    /**
     * Analyze given logger interface and generate
     * {@link LoggerInterfaceDescriptor} object that describes it.
     *
     * @param loggerInterface logger interface to describe
     *
     * @return logger interface descriptor
     */
    public static LoggerInterfaceDescriptor describeLoggerInterface (
        Class <? extends Logger> loggerInterface)
    {
        if (loggerInterface == null)
            throw new IllegalArgumentException ("Logger interface is null");

        if (!loggerInterface.isInterface ())
            throw new IllegalArgumentException (
                "Logger interface is not actually an interface: " +
                loggerInterface);

        if (Modifier.isPrivate (loggerInterface.getModifiers ()))
            throw new IllegalArgumentException (
                "Logger interface is private: " + loggerInterface);

        Field [] fields = loggerInterface.getFields ();
        List <LogLevelDescriptor> logLevelDescriptors =
            new ArrayList <LogLevelDescriptor> (fields.length);
        Set <Integer> levels = new HashSet <Integer> ();

        for (Field field: fields)
        {
            if (field.getAnnotation (LogLevel.class) != null)
            {
                if (!Integer.TYPE.equals (field.getType ()))
                    throw new IllegalArgumentException (
                        "Logging level constant is not of type int: " + field);

                int modifiers = field.getModifiers ();

                if (!Modifier.isPublic (modifiers))
                    throw new IllegalArgumentException (
                        "Logging level field is not public: " + field);

                if (!Modifier.isStatic (modifiers))
                    throw new IllegalArgumentException (
                        "Logging level field is not static:" + field);

                if (!Modifier.isFinal (modifiers))
                    throw new IllegalArgumentException (
                        "Logging level field is not final:" + field);

                int level;

                try
                {
                    level = field.getInt (null);
                }
                catch (IllegalAccessException ex)
                {
                    throw new Error (
                        "Cannot access logging level field value:" + field, ex);
                }

                if (!levels.add (Integer.valueOf (level)))
                    throw new IllegalArgumentException (
                        "Duplicate logging level: " + level);

                logLevelDescriptors.add (
                    new MyLogLevelDescriptor (field, field.getName (), level));
            }
        }

        Method [] methods = loggerInterface.getMethods ();
        List <LogLevelCheckerDescriptor> logLevelCheckerDescriptors =
            new ArrayList <LogLevelCheckerDescriptor> (methods.length);
        List <LogMessageDescriptor> logMessageDescriptors =
                new ArrayList <LogMessageDescriptor> (methods.length);

        for (Method method: methods)
        {
            LogLevelChecker logLevelCheckerAnnotation =
                method.getAnnotation (LogLevelChecker.class);
            if (logLevelCheckerAnnotation != null)
            {
                if (!Boolean.TYPE.equals (method.getReturnType ()))
                    throw new IllegalArgumentException (
                        "Logging level checker method return type is not boolean: " +
                            method);

                if (method.getParameterTypes ().length > 0)
                    throw new IllegalArgumentException (
                        "Logging level checker method accepts too many parameters: " +
                            method);


                int modifiers = method.getModifiers ();

                if (!Modifier.isPublic (modifiers))
                    throw new IllegalArgumentException (
                        "Logging level checker method is not public: " +
                            method);

                if (Modifier.isStatic (modifiers))
                    throw new IllegalArgumentException (
                        "Logging level checker method is static:" + method);

                logLevelCheckerDescriptors.add (
                    new MyLogLevelCheckerDescriptor (
                        method, logLevelCheckerAnnotation.level ()));
            }

            LogMessage logMessageAnnotation =
                method.getAnnotation (LogMessage.class);
            if (logMessageAnnotation != null)
            {
                if (!Void.TYPE.equals (method.getReturnType ()))
                    throw new IllegalArgumentException (
                        "Log message method return type is not void: " +
                            method);

                int modifiers = method.getModifiers ();

                if (!Modifier.isPublic (modifiers))
                    throw new IllegalArgumentException (
                        "Log message method is not public: " +
                            method);

                if (Modifier.isStatic (modifiers))
                    throw new IllegalArgumentException (
                        "Log message method is static:" + method);

                logMessageDescriptors.add (
                    new MyLogMessageDescriptor (
                        method,
                        logMessageAnnotation.level (),
                        logMessageAnnotation.template ()));
            }
        }

        return new MyLoggerInterfaceDescriptor (
            logLevelDescriptors.toArray (
                new LogLevelDescriptor [logLevelDescriptors.size ()]),
            logLevelCheckerDescriptors.toArray (
                new LogLevelCheckerDescriptor [logLevelCheckerDescriptors.size ()]),
            logMessageDescriptors.toArray (
                new LogMessageDescriptor [logMessageDescriptors.size ()]));
    }

    private static class MyLogLevelDescriptor implements LogLevelDescriptor
    {
        private final Field field;
        private final String name;
        private final int level;

        public MyLogLevelDescriptor (Field field, String name, int level)
        {
            if (field == null)
                throw new IllegalArgumentException ("Field is null");

            if (name == null)
                throw new IllegalArgumentException ("Name is null");

            this.field = field;
            this.name = name;
            this.level = level;
        }

        @Override
        public Field getField ()
        {
            return field;
        }

        @Override
        public String getName ()
        {
            return name;
        }

        @Override
        public int getLevel ()
        {
            return level;
        }
    }

    private static class MyLogLevelCheckerDescriptor
        implements LogLevelCheckerDescriptor
    {
        private final Method method;
        private final int level;

        public MyLogLevelCheckerDescriptor (Method method, int level)
        {
            if (method == null)
                throw new IllegalArgumentException ("Method is null");

            this.method = method;
            this.level = level;
        }

        @Override
        public Method getMethod ()
        {
            return method;
        }

        @Override
        public int getLevel ()
        {
            return level;
        }
    }

    private static class MyLogMessageDescriptor
        implements LogMessageDescriptor
    {
        private final Method method;
        private final int level;
        private final String template;

        public MyLogMessageDescriptor (Method method, int level, String template)
        {
            if (method == null)
                throw new IllegalArgumentException ("Method is null");

            if (template == null)
                throw new IllegalArgumentException ("Level is null");

            this.method = method;
            this.level = level;
            this.template = template;
        }

        @Override
        public Method getMethod ()
        {
            return method;
        }

        @Override
        public int getLevel ()
        {
            return level;
        }

        @Override
        public String getTemplate ()
        {
            return template;
        }
    }

    private static class MyLoggerInterfaceDescriptor
        implements LoggerInterfaceDescriptor
    {
        private final LogLevelDescriptor [] logLevelDescriptors;
        private final LogLevelCheckerDescriptor [] logLevelCheckerDescriptors;
        private final LogMessageDescriptor [] logMessageDescriptors;

        public MyLoggerInterfaceDescriptor (
            LogLevelDescriptor [] logLevelDescriptors,
            LogLevelCheckerDescriptor [] logLevelCheckerDescriptors,
            LogMessageDescriptor [] logMessageDescriptors)
        {
            if (logLevelDescriptors == null)
                throw new IllegalArgumentException (
                    "Log level descriptors is null");

            if (logLevelCheckerDescriptors == null)
                throw new IllegalArgumentException (
                    "Log level checker descritpros is null");

            if (logMessageDescriptors == null)
                throw new IllegalArgumentException (
                    "Log message descritpros is null");

            this.logLevelDescriptors = logLevelDescriptors.clone ();
            this.logLevelCheckerDescriptors =
                logLevelCheckerDescriptors.clone ();
            this.logMessageDescriptors = logMessageDescriptors.clone ();
        }

        @Override
        public int getLogLevelCount ()
        {
            return logLevelDescriptors.length;
        }

        @Override
        public LogLevelDescriptor getLogLevelAt (int index)
        {
            if (index < 0)
                throw new IllegalArgumentException ("Index < 0: " + index);

            int count = logLevelDescriptors.length;
            if (index >= count)
                throw new IllegalArgumentException ("Index(" + index + ") >= log level count(" + count + ")");


            return logLevelDescriptors [index];
        }

        @Override
        public int getLogLevelCheckerCount ()
        {
            return logLevelCheckerDescriptors.length;
        }

        @Override
        public LogLevelCheckerDescriptor getLogLevelCheckerAt (int index)
        {
            if (index < 0)
                throw new IllegalArgumentException ("Index < 0: " + index);

            int count = logLevelCheckerDescriptors.length;
            if (index >= count)
                throw new IllegalArgumentException ("Index(" + index + ") >= log level checker count(" + count + ")");


            return logLevelCheckerDescriptors [index];
        }

        @Override
        public int getLogMessageCount ()
        {
            return logMessageDescriptors.length;
        }

        @Override
        public LogMessageDescriptor getLogMessageAt (int index)
        {
            if (index < 0)
                throw new IllegalArgumentException ("Index < 0: " + index);

            int count = logMessageDescriptors.length;
            if (index >= count)
                throw new IllegalArgumentException ("Index(" + index + ") >= log message count(" + count + ")");


            return logMessageDescriptors [index];
        }
    }
}
